/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "ShadowMapGenerationProcess.h"

#include "../Components/MeshRenderer.h"
#include "../Content/MeshFilter.h"
#include "../Content/VertexShader.h"
#include "../Content/GeometryShader.h"
#include "../Content/PixelShader.h"

#include "../Content/RasterizerState.h"
#include "../Content/BlendState.h"
#include "../Content/SamplerState.h"


#include "../Components/MeshRenderer.h"

#include "../HLSLCPPDeclarations/Lighting.fx"
#include "../Managers/DX11Manager.h"
#include "../Directors/DX11Director.h"

#include "Hogshead.Common.h"


namespace Hogshead
{
	namespace DX11Rendering
	{
		ProcessCPP(Hogshead::DX11Rendering::ShadowMapGenerationProcess)


		ShadowMapGenerationProcess::ShadowMapGenerationProcess(void) :_world_view_projection_buffer(0)
		{
		}


		ShadowMapGenerationProcess::~ShadowMapGenerationProcess(void)
		{
		}

		void ShadowMapGenerationProcess::render(Camera* in_active_camera, HashSet<void*>* in_nonculled_components)
		{
			//clear out all our knowledge about the state of things on the card
			_prev_vertex_shader = NULL;
			_prev_pixel_shader = NULL;
			_prev_geometry_shader = NULL;

			_prev_rasterizer_state = NULL;
			_prev_blend_state = NULL;
			_prev_sampler_state = NULL;

			D3D11_VIEWPORT shadow_map_viewport;
			shadow_map_viewport.Width = 1024;
			shadow_map_viewport.Height = 1024;
			shadow_map_viewport.MinDepth = 0.0f;
			shadow_map_viewport.MaxDepth = 1.0f;
			shadow_map_viewport.TopLeftX = 0;
			shadow_map_viewport.TopLeftY = 0;
			
			DX11Manager::setViewport( &shadow_map_viewport );

			//this line is a direct port, may not need it
			DX11Manager::setDepthStencilState(true, D3D11_DEPTH_WRITE_MASK_ALL, D3D11_COMPARISON_LESS);

			//we assume everything going through this process has the same constant buffer
			//layout at register 0, namely world, view, projection
			//generate maps for SpotLights
			for( int i = 0 ; i < MAX_SPOT_LIGHT; i++ )
			{
				if( _parent->getLighting()._spot_lights[i]._produce_shadow_map.x > 0.5f )
				{
					//bind no RTV, bind the correct DSV
					DX11Manager::setShadowMapWrite( _shadow_maps[i].depthStencilView() );

					//clear shadow map
					DX11Manager::clearDepthStencil( _shadow_maps[i].depthStencilView(), D3D11_CLEAR_STENCIL | D3D11_CLEAR_DEPTH, 1.0f, 0);

					//render everything that is registered to generate shadows
					renderShadowSpotLightMap(i, in_nonculled_components);

					DX11Manager::setRenderTargetsCustom(NULL, 0, NULL);
				}
			}
			//generate maps for DirectionalLights
			for( int j = 0 ; j < MAX_DIRECTIONAL_LIGHT; j++ )
			{
				if( _parent->getLighting()._directional_lights[j]._produce_shadow_map.x > 0.5f )
				{
					//bind no RTV, bind the correct DSV
					DX11Manager::setShadowMapWrite( _shadow_maps[j+MAX_SPOT_LIGHT].depthStencilView() );

					//clear shadow map
					DX11Manager::clearDepthStencil( _shadow_maps[j+MAX_SPOT_LIGHT].depthStencilView(), D3D11_CLEAR_STENCIL | D3D11_CLEAR_DEPTH, 1.0f, 0);

					//render everything that is registered to generate shadows
					renderShadowDirectionalLightMap(j, in_nonculled_components);

					DX11Manager::setRenderTargetsCustom(NULL, 0, NULL);
				}
			}
			//generate maps for PointLights

		}

		void ShadowMapGenerationProcess::renderShadowSpotLightMap(int in_index, HashSet<void*>* in_nonculled_components)
		{
			MeshRenderer* mesh;
			const LightingInfo& li = _parent->getLighting();

			for( int i = 0; i < _shadow_casters.size(); i++ )
			{
				mesh = _shadow_casters[i]->as<MeshRenderer>();
				
				//if this object isn't in view of the camera, we don't care about it so continue
				if(!mesh->neverCull() && !in_nonculled_components->contains(_shadow_casters[i]) && _culling)
					continue;

				const Technique* current_draw_tech;
			
				current_draw_tech = mesh->getTechnique(this->_handle);

				//do each pass in the technique
				for(int j = 0; j < current_draw_tech->passes().size(); j++)
				{

					Pass* current_pass = current_draw_tech->passes()[j];

					//set topology
					DX11Manager::setTopology(current_pass->topology());			

					//------------------------
					//set the shaders
						// vertex
					if(!_prev_vertex_shader || current_pass->_vs != _prev_vertex_shader)
					{
						//for now just set the layout (check to be more optimal at a later time
						DX11Manager::setInputLayout( current_pass->_vs->_input_layout );
						//set the VS
						DX11Manager::setVertexShader( current_pass->_vs->_vs );
						//save it off as prev
						_prev_vertex_shader = current_pass->_vs;
					}
						//geometry
					if(!_prev_geometry_shader || current_pass->_gs != _prev_geometry_shader)
					{
						//check to see if there is a Geometry Shader
						if(current_pass->_gs)
						{
							//set the GS
							DX11Manager::setGeomentryShader( current_pass->_gs->_gs );
							//save it off as prev
							_prev_geometry_shader = current_pass->_gs;
						}
						else
						{
							//set the GS
							DX11Manager::setGeomentryShader( NULL );
							//save it off as prev
							_prev_geometry_shader = NULL;
						}
					}
						//pixel
					if(!_prev_pixel_shader || current_pass->_ps != _prev_pixel_shader)
					{
						//set the PS
						DX11Manager::setPixelShader( current_pass->_ps->_ps );
						//save it off as prev
						_prev_pixel_shader = current_pass->_ps;
					}

					
					//update some data we need on the wvp cbuffer and update it
					_world_view_projection._wvp =  li._spot_lights[in_index]._vp * mesh->getParentGameObject()->getTransform().getWorldMatrix();

					DX11Manager::bindConstantBuffer( &_world_view_projection_buffer, 0, VERTEX_SHADER );
					DX11Manager::updateConstantBuffer( _world_view_projection_buffer, &_world_view_projection );
				
					//get the rest from the component.
					for( int j = 0; j < mesh->constantBufferGroup().size(); j++ )
					{
						DX11Manager::bindConstantBuffer( mesh->constantBufferGroup().get(j).buffer(),
							mesh->constantBufferGroup().get(j).registerNumber(),
							mesh->constantBufferGroup().get(j).shaderBindFlags() );
					}

					//-----------------------
					//set other card states: blend, sampler, rasterizer
					if(!_prev_rasterizer_state || current_pass->_rasterizer_state != _prev_rasterizer_state )
					{
						_prev_rasterizer_state = current_pass->_rasterizer_state;
						DX11Manager::setRasterizerState(_prev_rasterizer_state->_rasterizer_state);
					}

					if(!_prev_blend_state || current_pass->_blend_state != _prev_blend_state)
					{
						_prev_blend_state = current_pass->_blend_state;
						DX11Manager::setBlendState( _prev_blend_state->_blend_state, NULL, 0xffffffff );
					}

					for( int j = 0; j < current_pass->_sampler_states.size(); j++ )
						DX11Manager::setSamplers(j, 1, &current_pass->_sampler_states[j]->_sampler_state, PIXEL_SHADER);


					//------------------------
					//finally iterate through each vertex buffer and set the buffer along
					//with the maps(Shader Resource Views)

					//NAIVE for now

					
					ID3D11Buffer* buffer_array[1] = {0};
					DX11Manager::setSOTargets(1, buffer_array);
					

					//buffer, number of primitives's(tri's), size of vertex
					const Vector<Triplet<ID3D11Buffer*, int, int>>& vertex_buffers = mesh->meshFilter()->_Vertex_Buffer_vector;
					for( int mesh_index = 0; mesh_index < vertex_buffers.size(); mesh_index++ )
					{
						//set the vertex buffer on the card
						DX11Manager::setVertexBuffer(vertex_buffers.get(mesh_index).first, vertex_buffers.get(mesh_index).third);

						//update the shader resources
						//more needs to be set here VIA techniques
						if(mesh->meshFilter()->_shader_resources[DIFFUSE].size() > 0)
						{
							if(mesh_index < mesh->meshFilter()->_shader_resources[DIFFUSE].size())
								DX11Manager::setPSShaderResource(DIFFUSE, &(mesh->meshFilter()->_shader_resources[DIFFUSE].get(mesh_index))->_shader_resource_view);
							else
								DX11Manager::setPSShaderResource(DIFFUSE, &(mesh->meshFilter()->_shader_resources[DIFFUSE].get(0))->_shader_resource_view);
						}

						

						//finally draw it !!!
						DX11Manager::draw(vertex_buffers.get(mesh_index).second, 0);			
					}	

				}
			}
		}

		void ShadowMapGenerationProcess::renderShadowDirectionalLightMap(int in_index, HashSet<void*>* in_nonculled_components)
		{
			MeshRenderer* mesh;
			const LightingInfo& li = _parent->getLighting();

			for( int i = 0; i < _shadow_casters.size(); i++ )
			{
				mesh = _shadow_casters[i]->as<MeshRenderer>();
				
				//if this object isn't in view of the camera, we don't care about it so continue
				if(!mesh->neverCull() && !in_nonculled_components->contains(_shadow_casters[i]) && _culling)
					continue;

				const Technique* current_draw_tech;
			
				current_draw_tech = mesh->getTechnique(this->_handle);

				//do each pass in the technique
				for(int j = 0; j < current_draw_tech->passes().size(); j++)
				{

					Pass* current_pass = current_draw_tech->passes()[j];

					//set topology
					DX11Manager::setTopology(current_pass->topology());			

					//------------------------
					//set the shaders
						// vertex
					if(!_prev_vertex_shader || current_pass->_vs != _prev_vertex_shader)
					{
						//for now just set the layout (check to be more optimal at a later time
						DX11Manager::setInputLayout( current_pass->_vs->_input_layout );
						//set the VS
						DX11Manager::setVertexShader( current_pass->_vs->_vs );
						//save it off as prev
						_prev_vertex_shader = current_pass->_vs;
					}
						//geometry
					if(!_prev_geometry_shader || current_pass->_gs != _prev_geometry_shader)
					{
						//check to see if there is a Geometry Shader
						if(current_pass->_gs)
						{
							//set the GS
							DX11Manager::setGeomentryShader( current_pass->_gs->_gs );
							//save it off as prev
							_prev_geometry_shader = current_pass->_gs;
						}
						else
						{
							//set the GS
							DX11Manager::setGeomentryShader( NULL );
							//save it off as prev
							_prev_geometry_shader = NULL;
						}
					}
						//pixel
					if(!_prev_pixel_shader || current_pass->_ps != _prev_pixel_shader)
					{
						//set the PS
						DX11Manager::setPixelShader( current_pass->_ps->_ps );
						//save it off as prev
						_prev_pixel_shader = current_pass->_ps;
					}

					
					//update some data we need on the wvp cbuffer and update it
					_world_view_projection._wvp =  li._directional_lights[in_index]._vp * mesh->getParentGameObject()->getTransform().getWorldMatrix();

					DX11Manager::bindConstantBuffer( &_world_view_projection_buffer, 0, VERTEX_SHADER );
					DX11Manager::updateConstantBuffer( _world_view_projection_buffer, &_world_view_projection );
				
					//get the rest from the component.
					for( int j = 0; j < mesh->constantBufferGroup().size(); j++ )
					{
						DX11Manager::bindConstantBuffer( mesh->constantBufferGroup().get(j).buffer(),
							mesh->constantBufferGroup().get(j).registerNumber(),
							mesh->constantBufferGroup().get(j).shaderBindFlags() );
					}

					//-----------------------
					//set other card states: blend, sampler, rasterizer
					if(!_prev_rasterizer_state || current_pass->_rasterizer_state != _prev_rasterizer_state )
					{
						_prev_rasterizer_state = current_pass->_rasterizer_state;
						DX11Manager::setRasterizerState(_prev_rasterizer_state->_rasterizer_state);
					}

					if(!_prev_blend_state || current_pass->_blend_state != _prev_blend_state)
					{
						_prev_blend_state = current_pass->_blend_state;
						DX11Manager::setBlendState( _prev_blend_state->_blend_state, NULL, 0xffffffff );
					}

					for( int j = 0; j < current_pass->_sampler_states.size(); j++ )
						DX11Manager::setSamplers(j, 1, &current_pass->_sampler_states[j]->_sampler_state, PIXEL_SHADER);

					//------------------------
					//finally iterate through each vertex buffer and set the buffer along
					//with the maps(Shader Resource Views)

					//NAIVE for now

					
					ID3D11Buffer* buffer_array[1] = {0};
					DX11Manager::setSOTargets(1, buffer_array);
					

					//buffer, number of primitives's(tri's), size of vertex
					const Vector<Triplet<ID3D11Buffer*, int, int>>& vertex_buffers = mesh->meshFilter()->_Vertex_Buffer_vector;
					for( int mesh_index = 0; mesh_index < vertex_buffers.size(); mesh_index++ )
					{
						//set the vertex buffer on the card
						DX11Manager::setVertexBuffer(vertex_buffers.get(mesh_index).first, vertex_buffers.get(mesh_index).third);

						//update the shader resources
						//more needs to be set here VIA techniques
						if(mesh->meshFilter()->_shader_resources[DIFFUSE].size() > 0)
						{
							if(mesh_index < mesh->meshFilter()->_shader_resources[DIFFUSE].size())
								DX11Manager::setPSShaderResource(DIFFUSE, &(mesh->meshFilter()->_shader_resources[DIFFUSE].get(mesh_index))->_shader_resource_view);
							else
								DX11Manager::setPSShaderResource(DIFFUSE, &(mesh->meshFilter()->_shader_resources[DIFFUSE].get(0))->_shader_resource_view);
						}

						

						//finally draw it !!!
						DX11Manager::draw(vertex_buffers.get(mesh_index).second, 0);			
					}	

				}
			}
		}


		void ShadowMapGenerationProcess::initialize()
		{

			this->_enabled = true;

			//create the main cbuffer
			if(!_world_view_projection_buffer)
				DX11Manager::createConstantBuffer( sizeof(WVP), &_world_view_projection_buffer );

			//create the 24 shadow maps
			//descriptions
			D3D11_TEXTURE2D_DESC tex_desc;
			ZeroMemory( &tex_desc, sizeof( D3D11_TEXTURE2D_DESC) );

			D3D11_DEPTH_STENCIL_VIEW_DESC dsv_desc;
			ZeroMemory( &dsv_desc, sizeof( D3D11_DEPTH_STENCIL_VIEW_DESC) );

			D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
			ZeroMemory( &srv_desc, sizeof( D3D11_SHADER_RESOURCE_VIEW_DESC) );

			//texture description
			tex_desc.Width = 1024;
			tex_desc.Height = 1024;
			tex_desc.MipLevels = 1;
			tex_desc.ArraySize = 1;
			tex_desc.Format = DXGI_FORMAT_R32_TYPELESS;
			tex_desc.SampleDesc.Count = 1;
			tex_desc.SampleDesc.Quality = 0;
			tex_desc.Usage = D3D11_USAGE_DEFAULT;
			tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
			tex_desc.CPUAccessFlags = 0;
			tex_desc.MiscFlags = 0;

			//Depth Stencil description
			dsv_desc.Format = DXGI_FORMAT_D32_FLOAT;
			dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
			dsv_desc.Texture2D.MipSlice = 0;
	
			//Shader Resource description.
			srv_desc.Format = DXGI_FORMAT_R32_FLOAT;
			srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
			srv_desc.Texture2D.MipLevels = tex_desc.MipLevels;
			srv_desc.Texture2D.MostDetailedMip = 0;

			StringBuilder sb;
			

			//using the descriptions, build the 24 maps
			for( int i = 0; i < 24; i++ )
			{
				sb.reset();
				sb << "Shadow Map " << i;

				const String& name = sb;

				_shadow_maps[i].build( tex_desc, NULL, &dsv_desc, &srv_desc, name, name.length() );
			}

		}

		void ShadowMapGenerationProcess::finalize()
		{
			if( _world_view_projection_buffer )
			{
				_world_view_projection_buffer->Release();
				_world_view_projection_buffer = NULL;
			}

			for( int i = 0; i < 24; i++ )
				_shadow_maps[i].finalize();
		}

		void ShadowMapGenerationProcess::registerComponent(IComponent* in_component)
		{
			if( in_component->is<MeshRenderer>() )
				_shadow_casters.add( in_component->as<MeshRenderer>() );
		}

		void ShadowMapGenerationProcess::unregisterComponent(IComponent* in_component)
		{
			if(in_component->is<MeshRenderer>())
			{
				//start from the end work forward, odds are this will be called most frequently
				//on objects that get turned on and off so they should be towards the last half
				//of the vector
				for(int i = _shadow_casters.size()-1; i > -1 ; i--)
				{
					if(in_component == _shadow_casters[i])
					{
						_shadow_casters.swap(i, _shadow_casters.size() - 1);
						_shadow_casters.removeLast();
						return;
					}
				}
			}
		}

		RenderingTextureBuffer* ShadowMapGenerationProcess::getShadowMaps()
		{
			return &_shadow_maps[0];
		}
	}
}